package cn.yedong.org.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//生成domain或实体类的工具类
public class EntityUtil {
    //数据库四大金刚
    private static String driverClassName = "com.mysql.jdbc.Driver";
    private static String url = "jdbc:mysql:///${database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false";
    private static String database = "pethome";
    private static String username = "root";
    private static String password = "123456";

    //生成类的包路径
    private static String packageName = "cn.yedong.org.domain";
    //忽略的表名前缀
    private static String ignoredTablePrefix = "t_";
    //列名下划线转小驼峰
    private static boolean columnToHump = true;
    //使用looooooooooooooooooombok
    private static boolean useLombok = false; //根据下方的开关添加对应注解
    //自动导包
    private static boolean autoImport = true;
    //生成无参构造
    private static boolean noArgsConstructor = true;
    //生成全参构造
    private static boolean allArgsConstructor = true;
    //生成无主键构造
    private static boolean noPrimaryArgsConstructor = true;
    //生成Getter和Setter方法
    private static boolean getterAndSetter = true;
    //生成toString方法
    private static boolean toString = true;
    //生成equals和hashCode方法
    private static boolean equalsAndHashCode = true;
    private static boolean useObjectsMethod = false; //使用Objects中的equals和HashCode方法 Java 7+
    //数据库time类型字段对应的Java类型
    private static Class timeOfType = Date.class;
    //数据库datetime类型字段对应的Java类型
    private static Class datetimeOfType = Date.class;
    //为Date添加格式化注解
    private static boolean addFormatAnno = true;
    private static String formatPattern = "yyy-MM-dd HH:mm:ss";


    //========================================================================
    public static void main(String[] args) {
        action();
    }
    //========================================================================


    private static Scanner scanner = new Scanner(System.in);
    private static Connection conn = null;
    private static DatabaseMetaData dbmd = null;
    private static Pattern linePattern = Pattern.compile("_(\\w)");

    /**
     * 总控制器
     */
    private static void action(){
        System.out.println("产品说明：此产品用于自动生成指定数据库中已存在表的实体类！");
        System.out.println("使用需知：运行之前先打开这个文件检查数据库名称密码等信息，否则连接数据库会失败！");
        System.out.println("==================================================================================");
        List<String> tables = getAllTables();
        if(tables == null) return;
        if(tables.size() == 0){
            System.err.println("当前数据库中没有表");
            return;
        }else {
            System.out.println("(a) 所有表\t(s) 手动输入表名");
        }
        for (int i = 0; i < tables.size(); i++) {
            System.out.print("("+(i+1)+") " + tables.get(i) + "\t");
            if((i+1)%5 == 0) System.out.println();
        }
        if(tables.size()%5 != 0) System.out.println();
        System.out.println("请选择要生成实体类的表名序号(多个使用空格或逗号隔开):");
        String line = scanner.nextLine();
        List<String> selectList = getSelectList(line);
        if(selectList.contains("a")){
            for(String table : tables){
                entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
            }
            System.out.println("请刷新你的工程...");
            return;
        }
        if(selectList.contains("s")){
            System.out.println("请输入要生成实体类的表名(多个使用空格或逗号隔开):");
            String tableLine = scanner.nextLine();
            List<String> tableList = getSelectList(tableLine);
            boolean flag = false;
            for (String table : tableList) {
                table = table.trim();
                if(table.length() != 0) {
                    entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
                    flag = true;
                }
            }
            if(flag){
                System.out.println("请刷新你的工程...");
            }else {
                System.out.println("未选中任何表...");
            }
            return;
        }
        ArrayList<String> tableList = new ArrayList<>();
        for (String select : selectList) {
            select = select.trim();
            try{
                int index = Integer.parseInt(select);
                tableList.add(tables.get(index-1));
            }catch (Exception e){}
        }
        for (String table : tableList) {
            entityCreater(getAllColumn(table), getAutoIncrementColumn(table), table);
        }
        if(tableList.size() != 0){
            System.out.println("请刷新你的工程...");
        }else {
            System.out.println("未选中任何表...");
        }

    }

    /**
     * 分割输入的数据
     * @param line
     * @return
     */
    private static List<String> getSelectList(String line){
        line = line.trim();
        line = line.replaceAll(" ",",");
        line = line.replaceAll("，",",");
        String[] selects = line.split(",");
        return Arrays.asList(selects);
    }

    /**
     * 获取指定表中所有的自增长列的相关信息
     * @return
     */
    private static List<String> getAutoIncrementColumn(String table){
        List<String> autoIncrementColumnMessage = new ArrayList<String>();
        try {
            dbmd = getConnectionByConfigFile().getMetaData();
            ResultSet colRet = dbmd.getColumns(null,"%",table,"%");
            while(colRet.next()) {
                if("YES".equals(colRet.getString("IS_AUTOINCREMENT"))){
                    autoIncrementColumnMessage.add(colRet.getString("COLUMN_NAME"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return autoIncrementColumnMessage;
    }

    /**
     * 获取指定表中所有的列的相关信息
     * @return
     */
    private static HashMap<String,String> getAllColumn(String table){
        HashMap<String,String> columnMessage = new LinkedHashMap<String,String>();
        PreparedStatement ps;
        try {
            ps = getConnectionByConfigFile().prepareStatement("select * from " + table);
            ResultSet rs = ps.executeQuery();
            ResultSetMetaData meta =  rs.getMetaData();
            for(int i = 1;i <= meta.getColumnCount();i++){
                String columnClassName = meta.getColumnClassName(i);
                if(datetimeOfType!=null && "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
                    columnClassName = datetimeOfType.toString().substring(6);
                }if(timeOfType!=null && "java.sql.Time".equalsIgnoreCase(columnClassName)){
                    columnClassName = timeOfType.toString().substring(6);
                }else if("[B".equals(columnClassName)){
                    columnClassName = "java.lang.String";
                }
                columnMessage.put(meta.getColumnName(i), columnClassName);
            }
        } catch (Exception e) {
            System.err.println("语句对象创建异常!");
            System.err.println(e.getMessage());
            return null;
        }
        return columnMessage;
    }

    /**
     * 获取该数据库中所有的表组成的集合，保存的是数据库表的名称
     * @return
     */
    private static List<String> getAllTables(){
        List<String> tables = new ArrayList<String>();
        try {
            dbmd = getConnectionByConfigFile().getMetaData();
            if(dbmd == null) return null;
            ResultSet rs = dbmd.getTables(null, "%","%",new String[]{"TABLE"});
            while(rs.next()){
                tables.add(rs.getString("TABLE_NAME"));
            }
        } catch (Exception e) {
            System.err.println("获取表元数据异常!");
            e.printStackTrace();
        }
        return tables;
    }

    /**
     * 通过属性配置文件.properties获取连接
     * @return
     */
    private static Connection getConnectionByConfigFile(){
        database.length(); //此行没用, 只是为了让IDE检查到此属性被使用
        if(conn != null) return conn;
        try {
            Class.forName(driverClassName);
            conn = DriverManager.getConnection(replacePlaceholder(url), username, password);
            return conn;
        } catch (ClassNotFoundException e) {
            System.err.println("驱动加载异常：是否忘记导入驱动包？ 驱动名写错？");
            e.printStackTrace();
        } catch (SQLException e) {
            System.err.println("获取连接异常：用户名和密码写错？ mysql服务尚未打开？ url格式错误？");
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 替换占位符内容
     * @param str
     * @return
     */
    private static String replacePlaceholder(String str){
        if(str == null) return str;
        int preIndex = str.indexOf("${");
        if(preIndex == -1) return str;
        int sufIndex = str.indexOf("}",preIndex+2);
        if(sufIndex == -1) return str;
        Class<?> clazz = MethodHandles.lookup().lookupClass();
        try {
            String fieldName = str.substring(preIndex + 2, sufIndex);
            Field field = clazz.getDeclaredField(fieldName);
            String fieldValue = (String)field.get(clazz);
            if(fieldValue == null) fieldValue = "";
            str = str.substring(0,preIndex) + fieldValue + str.substring(sufIndex+1);
        }catch (Exception e){
            System.err.println("替换占位符失败: " + str);
            e.printStackTrace();
        }
        return str;
    }

    /**
     * 生成实体类
     * @param columnMessage
     * @param autoIncrementColumnMessage
     * @param tableName
     */
    private static void entityCreater(HashMap<String,String> columnMessage,List<String> autoIncrementColumnMessage,String tableName){
        if(columnMessage == null) return;
        String sourcePath = getSourcePath();
        String packageName = getPackageName();
        String writePath = sourcePath + packageName.replaceAll("\\.", "/") + "/";
        if(ignoredTablePrefix != null && ignoredTablePrefix.length() != 0 && tableName.startsWith(ignoredTablePrefix)) {
            tableName = tableName.substring(ignoredTablePrefix.length());
        }
        String fileName = lineToBigHump(tableName);
        File file = new File(writePath);
        if(!file.exists()){
            file.mkdirs();
        }
        try{
            LinkedHashSet<String> annotationSet = new LinkedHashSet<>();
            LinkedHashSet<String> importSet = new LinkedHashSet<>();
            LinkedHashMap<String, String> propertyMessage = new LinkedHashMap<>();
            for(Map.Entry<String, String> entry:columnMessage.entrySet()){
                String columnClassName = entry.getValue();
                if(datetimeOfType!=null && "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
                    columnClassName = datetimeOfType.toString().substring(6);
                    importSet.add(columnClassName);
                }else if(timeOfType!=null && "java.sql.Time".equalsIgnoreCase(columnClassName)){
                    columnClassName = timeOfType.toString().substring(6);
                    importSet.add(columnClassName);
                }else if("[B".equals(columnClassName)){
                    columnClassName = "java.lang.String";
                }
                if("java.math.BigDecimal".equalsIgnoreCase(columnClassName)
                        || "java.sql.Time".equalsIgnoreCase(columnClassName)
                        || "java.sql.Timestamp".equalsIgnoreCase(columnClassName)){
                    importSet.add(columnClassName);
                }else if("java.sql.Date".equalsIgnoreCase(columnClassName)
                        || "java.util.Date".equalsIgnoreCase(columnClassName)){
                    importSet.add("java.util.Date");
                }

                String propertyName = entry.getKey();
                String propertyType = entry.getValue().substring(entry.getValue().lastIndexOf(".") + 1);
                if(columnToHump){
                    propertyName = lineToSmallHump(propertyName);
                }
                propertyMessage.put(propertyName, propertyType);
            }
            ArrayList<String> autoIncrementPropertyList = new ArrayList<>();
            for(String column: autoIncrementColumnMessage){
                if(columnToHump){
                    autoIncrementPropertyList.add(lineToSmallHump(column));
                }else {
                    autoIncrementPropertyList.add(column);
                }
            }
            int propertySize = propertyMessage.size();
            FileOutputStream fos = new FileOutputStream(writePath + fileName + ".java");
            StringBuilder word = new StringBuilder(765 * propertySize);
            StringBuilder tmpWord = new StringBuilder(128 * propertySize);;

            //初始化内容段

            //属性
            StringBuilder fieldWord = new StringBuilder(64 * propertySize);
            for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                //是否添加格式化注解
                if(addFormatAnno && "Date".equals(entry.getValue())){
                    fieldWord.append("\t@DateTimeFormat(pattern = \"").append(formatPattern).append("\")\r\n");
                    fieldWord.append("\t@JsonFormat(pattern = \"").append(formatPattern).append("\",timezone=\"GMT+8\")\r\n");
                    importSet.add("org.springframework.format.annotation.DateTimeFormat");
                    importSet.add("com.fasterxml.jackson.annotation.JsonFormat");
                }
                fieldWord.append("\tprivate ").append(entry.getValue()).append(" ").append(entry.getKey()).append(";\r\n");
            }

            StringBuilder constructorWord = new StringBuilder(128 * propertySize);
            //无参构造
            if(noArgsConstructor){
                if(useLombok){
                    annotationSet.add("@NoArgsConstructor");
                    importSet.add("lombok.NoArgsConstructor");
                }else {
                    constructorWord.append("\r\n\tpublic ").append(fileName).append("() {\r\n");
//					constructorWord.append("\t\tsuper();\r\n");
                    constructorWord.append("\t}\r\n");
                }
            }
            //第一遍全参数构造器, 第二遍无主键构造器
            for (int i = 0; i < 2; i++) {
                if(!allArgsConstructor && i==0) continue; //是否添加全参构造
                if(useLombok && i==0){
                    annotationSet.add("@AllArgsConstructor");
                    importSet.add("lombok.AllArgsConstructor");
                }else {
                    tmpWord.setLength(0);
                    tmpWord.append("\r\n\tpublic ").append(fileName).append("(");
                    for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                        if(i==1 && autoIncrementPropertyList.contains(entry.getKey())) continue;
                        tmpWord.append(entry.getValue()).append(" ").append(entry.getKey()).append(", ");
                    }
                    constructorWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(",")));
                    constructorWord.append(") {\r\n");
//					constructorWord.append("\t\tsuper();\r\n");
                    tmpWord = new StringBuilder();
                    for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                        if(i==1 && autoIncrementPropertyList.contains(entry.getKey())) continue;
                        tmpWord.append("\t\tthis.").append(entry.getKey()).append(" = ").append(entry.getKey()).append(";\r\n");
                    }
                    tmpWord.append("\t}\r\n");
                    constructorWord.append(tmpWord);
                }
                if(!noPrimaryArgsConstructor) break; //是否添加无主键构造
            }

            StringBuilder getterAndSetterWord = null;
            StringBuilder toStringWord = null;
            StringBuilder equalsAndHashCodeWord = null;
            if(useLombok){
                if(getterAndSetter && toString && equalsAndHashCode){
                    annotationSet.add("@Data");
                    importSet.add("lombok.Data");
                }else {
                    if(getterAndSetter){
                        annotationSet.add("@Getter");
                        annotationSet.add("@Setter");
                        importSet.add("lombok.Getter");
                        importSet.add("lombok.Setter");
                    }
                    if(toString){
                        annotationSet.add("@ToString");
                        importSet.add("lombok.ToString");
                    }
                    if(equalsAndHashCode){
                        annotationSet.add("@EqualsAndHashCode");
                        importSet.add("lombok.EqualsAndHashCode");
                    }
                }
            }else {
                //getter和setter方法
                if(getterAndSetter){
                    getterAndSetterWord = new StringBuilder(160 * propertySize);
                    getterAndSetterWord.append("\r\n");
                    for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                        String propertyBigHump = entry.getKey().substring(0,1).toUpperCase()+entry.getKey().substring(1);
                        getterAndSetterWord.append("\tpublic ").append(entry.getValue()).append(" get").append(propertyBigHump).append("() {\r\n")
                                .append("\t\treturn ").append(entry.getKey()).append(";\r\n\t}\r\n")
                                .append("\tpublic void set").append(propertyBigHump).append("(").append(entry.getValue())
                                .append(" ").append(entry.getKey()).append(") {\r\n").append("\t\t")
                                .append("this.").append(entry.getKey()).append(" = ").append(entry.getKey()).append(";\r\n\t}\r\n");
                    }
                }
                //toString()
                if(toString){
                    toStringWord = new StringBuilder(64 * propertySize);
                    String projectPath = getProjectPath();
                    boolean isEclipsePro = new File(projectPath + "/.project").exists();
                    toStringWord.append("\r\n\t@Override\r\n");
                    toStringWord.append("\tpublic String toString() {\r\n")
                            .append("\t\treturn \"").append(fileName);
                    if(isEclipsePro) toStringWord.append(" [");
                    else toStringWord.append("{");
                    toStringWord.append("\" +\r\n");
                    tmpWord.setLength(0);
                    for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                        if(!isEclipsePro && "String".equals(entry.getValue())){
                            tmpWord.append("\t\t\t\t\", ").append(entry.getKey()).append("='\" + ").append(entry.getKey()).append(" + '\\''").append(" +\r\n");
                        }else {
                            tmpWord.append("\t\t\t\t\", ").append(entry.getKey()).append("=\" + ").append(entry.getKey()).append(" +\r\n");
                        }
                    }
                    toStringWord.append("\t\t\t\t\"").append(tmpWord.substring(tmpWord.indexOf(",")+2)).append("\t\t\t\t'");
                    if(isEclipsePro) toStringWord.append("]");
                    else toStringWord.append("}");
                    toStringWord.append("';\r\n\t}\r\n");
                }
                //equals和hashCode
                if(equalsAndHashCode){
                    equalsAndHashCodeWord = new StringBuilder(128 * propertySize);
                    if(useObjectsMethod){ //使用Objects方法
                        importSet.add("java.util.Objects");
                        //equals
                        tmpWord.setLength(0);
                        tmpWord.append("\r\n\t@Override\r\n");
                        tmpWord.append("\tpublic boolean equals(Object o) {\r\n")
                                .append("\t\tif (this == o) return true;\r\n")
                                .append("\t\tif (o == null || getClass() != o.getClass()) return false;\r\n")
                                .append("\t\t").append(fileName).append(" that = (").append(fileName).append(") o;\r\n")
                                .append("\t\treturn ");
                        for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                            tmpWord.append("Objects.equals(").append(entry.getKey()).append(", that.")
                                    .append(entry.getKey()).append(") && ");
                        }
                        equalsAndHashCodeWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(" &")));
                        equalsAndHashCodeWord.append(";\r\n\t}\r\n");
                        //hashCode
                        tmpWord.setLength(0);
                        tmpWord.append("\r\n\t@Override\r\n");
                        tmpWord.append("\tpublic int hashCode() {\r\n")
                                .append("\t\treturn Objects.hash(");
                        for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                            tmpWord.append(entry.getKey()).append(", ");
                        }
                        equalsAndHashCodeWord.append(tmpWord.substring(0, tmpWord.lastIndexOf(",")));
                        equalsAndHashCodeWord.append(");\r\n\t}\r\n");
                    }else { //使用默认方法
                        //equals
                        tmpWord.setLength(0);
                        tmpWord.append("\r\n\t@Override\r\n");
                        tmpWord.append("\tpublic boolean equals(Object o) {\r\n")
                                .append("\t\tif (this == o) return true;\r\n")
                                .append("\t\tif (o == null || getClass() != o.getClass()) return false;\r\n")
                                .append("\t\t").append(fileName).append(" that = (").append(fileName).append(") o;\r\n");
                        int i = 1;
                        for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                            if(i != propertyMessage.size()){
                                tmpWord.append("\t\tif (").append(entry.getKey()).append(" != null ? !")
                                        .append(entry.getKey()).append(".equals(that.").append(entry.getKey())
                                        .append(") : that.").append(entry.getKey()).append(" != null) return false;\r\n");
                            }else {
                                tmpWord.append("\t\treturn ").append(entry.getKey()).append(" != null ? ")
                                        .append(entry.getKey()).append(".equals(that.").append(entry.getKey())
                                        .append(") : that.").append(entry.getKey()).append(" == null;\r\n");
                            }
                            i++;
                        }
                        equalsAndHashCodeWord.append(tmpWord).append("\t}\r\n");
                        //hashCode
                        tmpWord.setLength(0);
                        tmpWord.append("\r\n\t@Override\r\n");
                        tmpWord.append("\tpublic int hashCode() {\r\n");
                        i = 1;
                        for(Map.Entry<String, String> entry:propertyMessage.entrySet()){
                            if(i == 1){
                                tmpWord.append("\t\tint result = ").append(entry.getKey())
                                        .append(" != null ? ").append(entry.getKey()).append(".hashCode() : 0;\r\n");
                            }else {
                                tmpWord.append("\t\tresult = 31 * result + (").append(entry.getKey())
                                        .append(" != null ? ").append(entry.getKey()).append(".hashCode() : 0);\r\n");
                            }
                            i++;
                        }
                        tmpWord.append("\t\treturn result;\r\n");
                        equalsAndHashCodeWord.append(tmpWord).append("\t}\r\n");
                    }
                }
            }

            //自动导包
            StringBuilder importWord = null;
            if(autoImport && importSet.size()!=0){
                importWord = new StringBuilder("\r\n");
                for (String type : importSet) {
                    importWord.append("import ").append(type).append(";\r\n");
                }
            }

            //类注解
            StringBuilder annotationWord = null;
            if(annotationSet.size()!=0){
                annotationWord = new StringBuilder();
                for (String annotation : annotationSet) {
                    annotationWord.append("\r\n").append(annotation);
                }
            }

            //拼装
            if(packageName.length() != 0) word.append("package ").append(packageName).append(";\r\n");
            if(importWord != null) word.append(importWord);
            if(annotationWord != null) word.append(annotationWord);
            word.append("\r\npublic class ").append(fileName).append(" {\r\n");
            word.append(fieldWord);
            word.append(constructorWord);
            if(getterAndSetterWord != null) word.append(getterAndSetterWord);
            if(toStringWord != null) word.append(toStringWord);
            if(equalsAndHashCodeWord != null) word.append(equalsAndHashCodeWord);
            word.append("}");

            fos.write(word.toString().getBytes());
            fos.close();
        } catch (FileNotFoundException e) {
            System.err.println("文件没有找到!");
            e.printStackTrace();
            return;
        } catch (IOException e) {
            System.err.println("配置文件加载失败!");
            e.printStackTrace();
            return;
        }
    }

    /**
     * 下划线转小驼峰
     * @param str
     * @return
     */
    public static String lineToSmallHump(String str) {
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        if(sb.length() > 1){
            return sb.substring(0,1).toLowerCase() + sb.substring(1);
        }
        return sb.toString();
    }

    /**
     * 下划线转大驼峰
     * @param str
     * @return
     */
    public static String lineToBigHump(String str) {
        str = lineToSmallHump(str);
        if(str.length() == 0) return str;
        return str.substring(0,1).toUpperCase() + str.substring(1);
    }

    /**
     * 获取当前项目/子项目路径, 前面带一个/
     * @return
     */
    private static String getProjectPath(){
        String classesPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String projectPath = new File("").getAbsolutePath();
//        String subprojectName = classesPath.substring(projectPath.length() + 2, classesPath.indexOf("/", projectPath.length() + 2));
        String subprojectPath = classesPath.substring(0, classesPath.indexOf("/", projectPath.length() + 2));
        if(new File(subprojectPath + "/src").exists()){
            return subprojectPath;
        }
        return classesPath.substring(0, projectPath.length()+1);
    }

    /**
     * 获取src路径, 前面带一个/
     * @return
     */
    private static String getSourcePath(){
        String projectPath = getProjectPath();
        String javaPath = projectPath + "/src/main/java/";
        if(new File(javaPath).exists()){
            return javaPath;
        }
        return projectPath + "/src/";
    }

    /**
     * 获取包名
     * @return
     */
    private static String getPackageName(){
        String tmpPackageName = packageName.replaceAll("/", ".");
        tmpPackageName = tmpPackageName.replaceAll("\\\\", ".");
        if (tmpPackageName.startsWith(".")){
            tmpPackageName = tmpPackageName.substring(1);
        }
        if (tmpPackageName.endsWith(".")){
            tmpPackageName = tmpPackageName.substring(0, tmpPackageName.length()-1);
        }
        return tmpPackageName;
    }


}
